/* 
 * File:   JobControl.cpp
 * Author: weeteck
 * 
 * Created on 17. listopad 2012, 23:51
 */

#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <readline/history.h>

#include "Job.h"
#include "JobControl.h"

using std::list;

// globalni promenne
list<Job*> jobs_list;
int last_job_id = 0;


/**
 * Funkce prepne skupinu procesu s PGID child_pid do popredi a ceka na dokonceni nebo zastaveni
 * procesu s PID child_pid. Pak prepne na popredi zpet svou skupinu procesu.
 * 
 * @param child_pid PID procesu, na ktery ma funkce cekat
 * @return status procesu precteny funkci waitpid
 */
int wait_for_child(int child_pid)
{
  int status;

  if (tcsetpgrp(STDOUT_FILENO, child_pid))
    perror("tcsetpgrp");
  waitpid(child_pid, &status, WUNTRACED);
  tcsetpgrp(STDOUT_FILENO, getpid());

  return status;
}


/**
 * Nalezne v seznamu jobs_list ulohu s danym ID.
 * @param job_id ID hledane ulohy
 * @return job s pozadovanym ID
 */
Job* fndjob_id(int job_id)
{
  if (job_id < -1 || job_id > last_job_id || job_id == 0)
    return NULL;
  if (job_id == -1)
    return jobs_list.back();
  list<Job*>::iterator iter;
  for (iter = jobs_list.begin(); iter != jobs_list.end(); iter++)
    if ((*iter)->getId() == job_id)
      return (*iter);

  return NULL;
}


/**
 * Nalezne v seznamu jobs_list ulohu s danym PID.
 * @param job_pid PID hledane ulohy
 * @return job s pozadovanym PID
 */
Job* fndjob_pid(int job_pid)
{
  list<Job*>::iterator iter;
  for (iter = jobs_list.begin(); iter != jobs_list.end(); iter++)
    if ((*iter)->getPgid() == job_pid)
      return (*iter);

  return NULL;
}


/**
 * Smaze ulohu danou jejim ID.
 * Funkce odstrani ulohu ze seznamu jobs_list a uvolni pamet alokovanou pro job.
 * @param job_id ID ulohy
 */
void deljob(Job *job)
{
  delete job;
  jobs_list.remove(job);
}


/**
 * Funkce testuje status potomka po navratu z wait a vytiskne do konzole popis jeho stavu.
 * @param status status potomka zjisteny funkci wait
 * @param job uloha sdruzena s potomkem, pokud ulohu nema - NULL
 */
int test_child_status(int status, Job* job)
{
  if (WIFEXITED(status))
  {
    if (job != NULL)
    {
      printf("\n[%d]  %-11s  %s (%s: %d)\n", job->getId(), STR_EXITED, job->getCmd(),
                                             job->isPiped() ? "pgid" : "pid", job->getPgid());
    }
    return Job::EXITED;
  }
  else if (WIFSIGNALED(status))
  {
    if (job != NULL)
      printf("\n[%d]  ", job->getId());
    else
      printf("\n");
    switch (WTERMSIG(status))
    {
      case SIGINT:
        printf("%s (SIGINT)  ", STR_QUIT);
        break;
      case SIGQUIT:
        printf("%s (SIGQUIT) (%s)  ", STR_QUIT, STR_CORE_DUMPED);
        break;
      case SIGTERM:
        printf("%s (SIGTERM)  ", STR_QUIT);
        break;
      case SIGKILL:
        printf("%s (SIGKILL)  ", STR_KILLED);
        break;
      default:
        printf("%s  ", STR_QUIT);
    }
    if (job != NULL)
      printf("%s (%s: %d)\n", job->getCmd(), job->isPiped() ? "pgid" : "pid", job->getPgid());
    else
      printf("\n");
    return Job::QUITED;
  }
  else if (WIFSTOPPED(status))
  {
    if (job == NULL)
      printf("\n[%d]  %-11s  ", last_job_id + 1, STR_STOPPED);
    else
      printf("\n[%d]  %-11s  %s  (%s: %d)\n", job->getId(), STR_STOPPED, job->getCmd(),
                                              job->isPiped() ? "pgid" : "pid", job->getPgid());
    return Job::STOPPED;
  }
  else if (WIFCONTINUED(status))
  {
    if (job != NULL)
      printf("\n[%d]  %s &\n", job->getId(), job->getCmd());
    return Job::RUNNING;
  }
  
  return -1;
}


/* _______________
 * SIGNAL HANDLERS 
 */


/**
 * Nastavi signal handlery podle potreby - bud pro shell nebo pro child proces.
 * @param shell true pokud ma nastavit pro shell, false pro child procesy
 */
void set_sighdls(bool shell)
{
  if (shell)
  {
    signal(SIGINT, SIG_IGN); //  Ctrl + C 
    signal(SIGQUIT, SIG_IGN); /*  Ctrl + \ */
    signal(SIGTSTP, SIG_IGN); //  Ctrl + Z  
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTTOU, SIG_IGN);
    signal(SIGTERM, sigterm_hdl);
    signal(SIGCHLD, sigchld_hdl);

    /*struct sigaction action;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    action.sa_handler = &sigchld_hdl;
    if (sigaction(SIGCHLD, &action, 0))
      perror("sigaction");
    action.sa_handler = &sigterm_hdl;
    if (sigaction(SIGTERM, &action, 0))
      perror("sigaction");*/
  }
  else
  {
    signal(SIGINT, SIG_DFL); //  Ctrl + C  
    signal(SIGQUIT, SIG_DFL); /*  Ctrl + \ */
    signal(SIGTSTP, SIG_DFL); //  Ctrl + Z 
    signal(SIGTTIN, SIG_DFL);
    signal(SIGTTOU, SIG_DFL);
  }
}


/**
 * Signal handler, jeste neni zcela hotov...
 * @param sig
 */
void sigchld_hdl(int sig)
{
  int status;
  int pid;
  Job *job;

  if ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0)
  {
    job = fndjob_pid(pid);
    switch (test_child_status(status, job))
    {
      case Job::RUNNING:
        job->setState(Job::RUNNING);
        break;
      case Job::STOPPED:
        job->setState(Job::STOPPED);
        break;
      case Job::EXITED:
      case Job::QUITED:
      case Job::KILLED:
        deljob(job);
        break;
    }
  }
}


/**
 * Handler pro SIGTERM. Uklidi zaznamy o spustenych ulohach, pak zavre shell.
 * @param sig
 */
void sigterm_hdl(int sig)
{
  // nefunguje preklad na eryxu - starsi verze g++ zrejme nepodporuje standard c++0x
  /*for (Job *job : jobs_list)
    delete job;
  jobs_list.clear();*/

  while (jobs_list.size() > 0)
  {
    delete jobs_list.front();
    jobs_list.pop_front();
  }
  
  clear_history();
  printf("\n%s (SIGTERM)\n", STR_QUIT);
  exit(0);
}

